using System;

namespace RecurrenceGenerator
{
	#region Class-specific Enums
	/// <summary>
	/// The Regeneration type. 
	/// </summary>
	public enum WeeklyRegenType { NotSet = -1, OnEveryXWeeks };
	#endregion //Class-specific Enums

	#region Class-specific Structs
	public struct SelectedDayOfWeekValues
	{
		public bool Sunday;
		public bool Monday;
		public bool Tuesday;
		public bool Wednesday;
		public bool Thursday;
		public bool Friday;
		public bool Saturday;
	}
	#endregion //Class-specific Structs

	public class WeeklyRecurrenceSettings : RecurrenceSettings
	{
		#region Constructors

		/// <summary>
		/// Get dates by Start date only. This is for no ending date values.
		/// </summary>
		/// <param name="startDate"></param>
		public WeeklyRecurrenceSettings(DateTime startDate) : base(startDate) { }

		/// <summary>
		/// Get dates by Start and End date boundries.
		/// </summary>
		/// <param name="startDate"></param>
		/// <param name="endDate"></param>
		public WeeklyRecurrenceSettings(DateTime startDate, DateTime endDate) : base(startDate, endDate) { }

		/// <summary>
		/// Get dates by Start date and number of occurrences.
		/// </summary>
		/// <param name="startDate"></param>
		/// <param name="numberOfOccurrences"></param>
		public WeeklyRecurrenceSettings(DateTime startDate, int numberOfOccurrences) : base(startDate, numberOfOccurrences) { }
		
		#endregion

		#region Private Fields

		WeeklyRegenType regenType = WeeklyRegenType.OnEveryXWeeks;
		SelectedDayOfWeekValues selectedDays;
		int regenEveryXWeeks;
		bool getNextDateValue;
		DateTime nextDateValue;
		#endregion //Private Fields

		#region Public GetValues

		/// <summary>
		/// Get day values. This overload is for every x-weeks.
		/// </summary>
		/// <param name="regenEveryXDays">Interval of weeks. Every x-weeks.</param>
		/// <returns>RecurrenceValues</returns>
		public RecurrenceValues GetValues(int regenEveryXWeeks, SelectedDayOfWeekValues selectedDays)
		{
			this.regenEveryXWeeks = regenEveryXWeeks;
			regenType = WeeklyRegenType.OnEveryXWeeks;
			this.selectedDays = selectedDays;
			return GetValues();
		}

		#endregion //Public GetValues

		internal static RecurrenceInfo GetFriendlyRecurrenceInfo(string seriesInfo)
		{
			RecurrenceInfo info = new RecurrenceInfo();
			EndDateType endType = EndDateType.NotDefined;
			DateTime endDateValue = DateTime.MinValue;
			int noOccurrences;

			// Exit if not a Weekly seriesInfo type
			if (!seriesInfo.StartsWith("W"))
				return null;

			info.SetSeriesInfo(seriesInfo);
			info.SetRecurrenceType(RecurrenceType.Weekly);

			// FORMATTING DEFINITIONS
			//  Y = Yearly Designator
			//  |       0 = Start Date (8 chars)
			//  |       |        1 = End Date (8 chars)
			//  |       |        |        2 = Number of occurrences (4 chars)
			//  |       |        |        |      3 = Regeneration Type (1 char)
			//  |       |        |        |      |    4 = End date type (1 char)
			//  |       |        |        |      |    |      5 = Regenerate on Sunday
			//  |       |        |        |      |    |      |       6 = Regenerate on Monday
			//  |       |        |        |      |    |      |       |       7 = Regenerate on Tuesday
			//  |       |        |        |      |    |      |       |       |       8 = Regenerate on Wednesday
			//  |       |        |        |      |    |      |       |       |       |      9 = Regenerate on Thursday
			//  |       |        |        |      |    |      |       |       |       |      |      10 = Regenerate on Friday
			//  |       |        |        |      |    |      |       |       |       |      |       |      11 = Regenerate on Saturday
			//  |       |        |        |      |    |      |       |       |       |      |       |      |        12  Regen Every x weeks
			//  |       |        |        |      |    |      |       |       |       |      |       |      |         |
			// [0]    [1-8]    [9-16]  [17-20]  [21] [22]  [23]    [24]    [25]    [26]   [27]     [28]   [29]      [30-32]
			//  W   20071231  20171231   0000    1     1     T       T      F        F      F       F      F        000 
			string startDate = seriesInfo.Substring(1, 8);

			DateTime dtStartDate = new DateTime(int.Parse(startDate.Substring(0, 4)), int.Parse(startDate.Substring(4, 2)), int.Parse(startDate.Substring(6, 2)));
			string endDate = seriesInfo.Substring(9, 8);
			SelectedDayOfWeekValues selectedDays = new SelectedDayOfWeekValues();
			string occurrences = seriesInfo.Substring(17, 4);
			string weeklyRegenType = seriesInfo.Substring(21, 1);
			string endDateType = seriesInfo.Substring(22, 1);
			int regenXWeeks = int.Parse(seriesInfo.Substring(30, 3));

			endType = (EndDateType)(int.Parse(endDateType));
			noOccurrences = int.Parse(occurrences);

			selectedDays.Sunday = (seriesInfo.Substring(23, 1) == "Y") ? true : false;
			selectedDays.Monday = (seriesInfo.Substring(24, 1) == "Y") ? true : false;
			selectedDays.Tuesday = (seriesInfo.Substring(25, 1) == "Y") ? true : false;
			selectedDays.Wednesday = (seriesInfo.Substring(26, 1) == "Y") ? true : false;
			selectedDays.Thursday = (seriesInfo.Substring(27, 1) == "Y") ? true : false;
			selectedDays.Friday = (seriesInfo.Substring(28, 1) == "Y") ? true : false;
			selectedDays.Saturday = (seriesInfo.Substring(29, 1) == "Y") ? true : false;

			endType = (EndDateType)(int.Parse(endDateType));
			noOccurrences = int.Parse(occurrences);

			// Get the EndDate before any modifications on it are performed
			if (endType == EndDateType.SpecificDate)
				endDateValue = new DateTime(int.Parse(endDate.Substring(0, 4)), int.Parse(endDate.Substring(4, 2)), int.Parse(endDate.Substring(6, 2)));

			info.SetEndDateType(endType);
			// Determine the Constructor by the type of End Date.
			// All constructors start with a Start date at a minimum.
			// Determine the Constructor by the type of End Date.
			// All constructors start with a Start date at a minimum.
			switch (endType)
			{
				case EndDateType.NumberOfOccurrences:
					info.SetStartDate(dtStartDate);
					info.SetNumberOfOccurrences(noOccurrences);
					break;

				case EndDateType.SpecificDate:
					info.SetStartDate(dtStartDate);
					info.SetEndDate(endDateValue);
					break;

				case EndDateType.NoEndDate:
					info.SetStartDate(dtStartDate);
					break;
			}

			info.SetWeeklyRegenType((WeeklyRegenType)(int.Parse(weeklyRegenType)));
			// Determine the Type of dates to get, specific, custom, etc.
			switch (info.WeeklyRegenType)
			{
				case WeeklyRegenType.OnEveryXWeeks:
					info.SetSelectedDayOfWeekValues(selectedDays);
					info.SetRegenEveryXWeeks(regenXWeeks);
					break;

				case WeeklyRegenType.NotSet:

					break;
			}

			return info;


		}

		#region Internal GetValues

		/// <summary>
		///     Final overloaded function that gets the Recurrence Values. 
		///     This is called from the RecurrenceHelper staic methods only.
		/// </summary>
		/// <returns>
		///     A RecurrenceGenerator.RecurrenceValues value...
		/// </returns>
		internal override RecurrenceValues GetValues()
		{
			return GetRecurrenceValues();
		}

		internal override RecurrenceValues GetValues(DateTime startDate, DateTime endDate)
		{
			base.StartDate = startDate;
			base.EndDate = endDate;
			// Change the end type to End Date as this original series info
			// may have been set to number of occurrences.
			base.endDateType = EndDateType.SpecificDate;
			return GetRecurrenceValues();
		}

		internal override RecurrenceValues GetValues(DateTime startDate, int numberOfOccurrences)
		{
			base.NumberOfOccurrences = numberOfOccurrences;
			base.StartDate = startDate;
			// Change the end type to number of occurrences. 
			// This must be set because the original starting Series Info may
			// be set to have an End Date type.
			base.endDateType = EndDateType.NumberOfOccurrences;
			return GetRecurrenceValues();
		}

		RecurrenceValues GetRecurrenceValues()
		{
			RecurrenceValues values = null;
			switch (RegenType)
			{
				case WeeklyRegenType.OnEveryXWeeks:
					values = GetEveryXWeeksValues();
					break;

			}
			if (values.Values.Count > 0)
			{
				values.SetStartDate(values.Values[0]);

				// Get the end date if not open-ended
				if (base.TypeOfEndDate != EndDateType.NoEndDate)
					values.SetEndDate(values.Values[values.Values.Count - 1]);
			}
			// Set the Series information that's used to get the next date
			// values for no ending dates.
			values.SetSeriesInfo(GetSeriesInfo());

			return values;
		}

		#endregion //Internal GetValues

		#region Internal Procedures

		internal static string GetPatternDefinition(string seriesInfo)
		{
			string returnValue = string.Empty;
			returnValue =
			" WEEKLY FORMATTING DEFINITIONS \r\n" +
			"  Y = Yearly Designator \r\n" +
			"  |       0 = Start Date (8 chars) \r\n" +
			"  |       |        1 = End Date (8 chars) \r\n" +
			"  |       |        |        2 = Number of occurrences (4 chars) \r\n" +
			"  |       |        |        |      3 = Regeneration Type (1 char) \r\n" +
			"  |       |        |        |      |    4 = End date type (1 char) \r\n" +
			"  |       |        |        |      |    |      5 = Regenerate on Sunday \r\n" +
			"  |       |        |        |      |    |      |       6 = Regenerate on Monday \r\n" +
			"  |       |        |        |      |    |      |       |       7 = Regenerate on Tuesday \r\n" +
			"  |       |        |        |      |    |      |       |       |       8 = Regenerate on Wednesday \r\n" +
			"  |       |        |        |      |    |      |       |       |       |      9 = Regenerate on Thursday \r\n" +
			"  |       |        |        |      |    |      |       |       |       |      |      10 = Regenerate on Friday \r\n" +
			"  |       |        |        |      |    |      |       |       |       |      |       |      11 = Regenerate on Saturday \r\n" +
			"  |       |        |        |      |    |      |       |       |       |      |       |      |        12  Regen Every x weeks \r\n" +
			"  |       |        |        |      |    |      |       |       |       |      |       |      |         | \r\n" +
			" [0]    [1-8]    [9-16]  [17-20]  [21] [22]  [23]    [24]    [25]    [26]   [27]     [28]   [29]      [30-32] \r\n" +
			string.Format("  W   {0}  {1}   {2}    {3}     {4}     {5}       {6}       {7}      {8}       {9}       {10}      {11}         {12}\r\n", seriesInfo.Substring(1, 8), seriesInfo.Substring(9, 8), seriesInfo.Substring(17, 4), seriesInfo.Substring(21, 1), seriesInfo.Substring(22, 1), seriesInfo.Substring(23, 1), seriesInfo.Substring(24, 1), seriesInfo.Substring(25, 1), seriesInfo.Substring(26, 1), seriesInfo.Substring(27, 1), seriesInfo.Substring(28, 1), seriesInfo.Substring(29, 1), seriesInfo.Substring(30, 3));
			return returnValue;
		}

		/// <summary>
		/// Set the values in preperation for getting the Next date in the series.
		/// </summary>
		/// <param name="regenEveryXWeeks">Value to regenerate the dates every x-weeks</param>
		/// <param name="selectedDays">Struct of days selected for the week.</param>
		void SetValues(int regenEveryXWeeks, SelectedDayOfWeekValues selectedDays)
		{
			this.regenEveryXWeeks = regenEveryXWeeks;
			regenType = WeeklyRegenType.OnEveryXWeeks;
			this.selectedDays = selectedDays;
		}

		/// <summary>
		///     Get the next date
		/// </summary>
		/// <param name="currentDate" type="System.DateTime">
		///     <para>
		///         
		///     </para>
		/// </param>
		/// <returns>
		///     A System.DateTime value...
		/// </returns>
		internal override DateTime GetNextDate(DateTime currentDate)
		{
			getNextDateValue = true;
			nextDateValue = currentDate;
			// Now get the values and return the last date found.
			RecurrenceValues values = GetValues();
			return values.EndDate;
		}

		internal static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo)
		{
			return GetRecurrenceSettings(seriesInfo, -1, DateTime.MinValue);
		}

		internal static WeeklyRecurrenceSettings GetRecurrenceSettings(DateTime modifiedStartDate, string seriesInfo)
		{
			return GetRecurrenceSettings(seriesInfo, -1, modifiedStartDate, DateTime.MinValue);
		}

		internal static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo, int modifiedOccurrencesValue)
		{
			return GetRecurrenceSettings(seriesInfo, modifiedOccurrencesValue, DateTime.MinValue);
		}

		internal static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo, DateTime modifiedStartDate, int modifiedOccurrencesValue)
		{
			return GetRecurrenceSettings(seriesInfo, modifiedOccurrencesValue, modifiedStartDate, DateTime.MinValue);
		}

		internal static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo, DateTime modifiedEndDate)
		{
			return GetRecurrenceSettings(seriesInfo, -1, modifiedEndDate);
		}

		internal static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo, DateTime modifiedStartDate, DateTime modifiedEndDate)
		{
			return GetRecurrenceSettings(seriesInfo, -1, modifiedStartDate, modifiedEndDate);
		}

		static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo, int modifiedOccurrencesValue, DateTime modifiedStartDate, DateTime modifiedEndDate)
		{
			WeeklyRecurrenceSettings settings = null;
			RecurrenceInfo info = WeeklyRecurrenceSettings.GetFriendlyRecurrenceInfo(seriesInfo);

			// Check to see if this is to modify the SeriesInfo and run as endtype for occurrences
			if (modifiedOccurrencesValue != -1)
			{
				info.SetEndDateType(EndDateType.NumberOfOccurrences);
				info.SetNumberOfOccurrences(modifiedOccurrencesValue);
			}
			// Check to see if this is to modify the EndDate and run as endType for EndDate
			if (modifiedEndDate != DateTime.MinValue)
			{
				info.SetEndDateType(EndDateType.SpecificDate);
				info.SetEndDate(modifiedEndDate);
			}

			// Determine the Constructor by the type of End Date.
			// All constructors start with a Start date at a minimum.
			switch (info.EndDateType)
			{
				case EndDateType.NumberOfOccurrences:
					settings = new WeeklyRecurrenceSettings(modifiedStartDate, info.NumberOfOccurrences);
					break;

				case EndDateType.SpecificDate:
					settings = new WeeklyRecurrenceSettings(modifiedStartDate, info.EndDate.Value);
					break;

				case EndDateType.NoEndDate:
					settings = new WeeklyRecurrenceSettings(modifiedStartDate);
					break;
			}


			// Determine the Type of dates to get, specific, custom, etc.
			switch (info.WeeklyRegenType)
			{
				case WeeklyRegenType.OnEveryXWeeks:
					settings.SetValues(info.WeeklyRegenEveryXWeeks, info.WeeklySelectedDays);
					break;

				case WeeklyRegenType.NotSet:

					break;
			}

			return settings;

		}

		static WeeklyRecurrenceSettings GetRecurrenceSettings(string seriesInfo, int modifiedOccurrencesValue, DateTime modifiedEndDate)
		{
			RecurrenceInfo info = WeeklyRecurrenceSettings.GetFriendlyRecurrenceInfo(seriesInfo);

			return GetRecurrenceSettings(seriesInfo, modifiedOccurrencesValue, info.StartDate, modifiedEndDate);
		}

		#endregion //Internal Procedures

		#region Private Procedures

		/// <summary>
		/// Get the Series information that's used to get dates at a later
		/// date. This is passed into the RecurrenceHelper to get date values.
		/// Most likely used for non-ending dates.
		/// </summary>
		/// <returns></returns>
		string GetSeriesInfo()
		{
			string info = string.Empty;
			string endDate = "ZZZZZZZZ"; // Default for no ending date.
			string occurrences = base.NumberOfOccurrences.ToString("0000");
			string weeklyRegenType = ((int)regenType).ToString("0");
			string endDateType = ((int)base.TypeOfEndDate).ToString("0");
			string regenXWeeks = regenEveryXWeeks.ToString("000");
			string daySelectedSunday = selectedDays.Sunday ? "Y" : "N";
			string daySelectedMonday = selectedDays.Monday ? "Y" : "N";
			string daySelectedTuesday = selectedDays.Tuesday ? "Y" : "N";
			string daySelectedWednesday = selectedDays.Wednesday ? "Y" : "N";
			string daySelectedThursday = selectedDays.Thursday ? "Y" : "N";
			string daySelectedFriday = selectedDays.Friday ? "Y" : "N";
			string daySelectedSaturday = selectedDays.Saturday ? "Y" : "N";

			// End Date may be null if no ending date.
			if (base.EndDate.HasValue)
				endDate = base.EndDate.Value.ToString("yyyyMMdd");

			// FORMATTING DEFINITIONS
			//  Y = Yearly Designator
			//  |       0 = Start Date (8 chars)
			//  |       |        1 = End Date (8 chars)
			//  |       |        |        2 = Number of occurrences (4 chars)
			//  |       |        |        |      3 = Regeneration Type (1 char)
			//  |       |        |        |      |    4 = End date type (1 char)
			//  |       |        |        |      |    |      5 = Regenerate on Sunday
			//  |       |        |        |      |    |      |       6 = Regenerate on Monday
			//  |       |        |        |      |    |      |       |       7 = Regenerate on Tuesday
			//  |       |        |        |      |    |      |       |       |       8 = Regenerate on Wednesday
			//  |       |        |        |      |    |      |       |       |       |      9 = Regenerate on Thursday
			//  |       |        |        |      |    |      |       |       |       |      |      10 = Regenerate on Friday
			//  |       |        |        |      |    |      |       |       |       |      |       |      11 = Regenerate on Saturday
			//  |       |        |        |      |    |      |       |       |       |      |       |      |        12  Regen Every x weeks
			//  |       |        |        |      |    |      |       |       |       |      |       |      |         |
			// [0]    [1-8]    [9-16]  [17-20]  [21] [22]  [23]    [24]    [25]    [26]   [27]     [28]   [29]      [30]
			//  W   20071231  01082017   0000    1     1     T       T      F        F      F       F      F        000 
			info = string.Format("W{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}", base.StartDate.ToString("yyyyMMdd"), endDate, occurrences, weeklyRegenType, endDateType, daySelectedSunday, daySelectedMonday, daySelectedTuesday, daySelectedWednesday, daySelectedThursday, daySelectedFriday, daySelectedSaturday, regenXWeeks);
			return info;
		}

		RecurrenceValues GetEveryXWeeksValues()
		{
			RecurrenceValues values = new RecurrenceValues();
			DateTime dt = base.StartDate.AddDays(-1); // Backup a day so the first instance of GetNextDay will increment to the next day.

			if (getNextDateValue)
			{
				do
				{
					dt = GetNextDay(dt);
					values.AddDateValue(dt);
					if (values.Values[values.Values.Count - 1] > nextDateValue)
						break;
				} while (dt <= nextDateValue.AddDays((regenEveryXWeeks * 7) + 7)); // Ensure the last date if greater than what's needed for the next date in the cycle
			}
			else
			{
				switch (base.TypeOfEndDate)
				{
					case EndDateType.NoEndDate:
						throw new Exception("The ability to create recurring dates with no End date is not currently available.");

					case EndDateType.NumberOfOccurrences:

						for (int i = 0; i < base.NumberOfOccurrences; i++)
						{
							dt = GetNextDay(dt);
							values.AddDateValue(dt);
						}
						break;

					case EndDateType.SpecificDate:
						int occurrences = 0;
						do
						{
							dt = GetNextDay(dt);
							// Handle for dates past the end date
							if (dt > base.EndDate)
								break;

							values.AddDateValue(dt);
							occurrences++;

						} while (dt <= base.EndDate);

						NumberOfOccurrences = occurrences;
						break;

					default:
						throw new ArgumentNullException("TypeOfEndDate", "The TypeOfEndDate property has not been set.");
				}
			}

			return values;
		}

		DateTime GetNextDay(DateTime input)
		{
			DateTime? returnDate = null;

			// Get the return date by incrementing the date
			// and checking the value against the selected days
			// of the week.
			do
			{
				input = input.AddDays(1);
				switch (input.DayOfWeek)
				{
					case DayOfWeek.Sunday:
						if (selectedDays.Sunday)
							returnDate = input;
						break;
					case DayOfWeek.Monday:
						if (selectedDays.Monday)
							returnDate = input;
						break;
					case DayOfWeek.Tuesday:
						if (selectedDays.Tuesday)
							returnDate = input;
						break;
					case DayOfWeek.Wednesday:
						if (selectedDays.Wednesday)
							returnDate = input;
						break;
					case DayOfWeek.Thursday:
						if (selectedDays.Thursday)
							returnDate = input;
						break;
					case DayOfWeek.Friday:
						if (selectedDays.Friday)
							returnDate = input;
						break;
					case DayOfWeek.Saturday:
						if (selectedDays.Saturday)
							returnDate = input;
						else
						{
							// Increment by weeks if regenXWeeks has a value 
							// greater than 1 which is default.
							// But only increment if we've gone over
							// at least 7 days already.
							if (regenEveryXWeeks > 1)
								input = input.AddDays((regenEveryXWeeks - 1) * 7);
						}
						break;
				}
			} while (!returnDate.HasValue);
			return returnDate.Value;
		}

		#endregion //Private Procedures

		#region Public Fields

		/// <summary>
		/// What is the interval to generate dates. This is used to skip weeks in the cycle.
		/// </summary>
		public int RegenEveryXWeeks
		{
			get
			{
				return regenEveryXWeeks;
			}
		}

		public SelectedDayOfWeekValues SelectedDays
		{
			get
			{
				return selectedDays;
			}
		}

		/// <summary>
		/// What is the regeneration type such as Specific day of month, custom date, etc.
		/// </summary>
		public WeeklyRegenType RegenType
		{
			get
			{
				return regenType;
			}
		}

		#endregion //Public Fields
	}
}
